using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using HDRSample;

namespace Space
{
    public enum Stage
    {
        Menu, Game
    }

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game, IGameActions, IGraphics
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        public SpriteBatch SpriteBatch { get { return spriteBatch; } }

        Dictionary<string, Model> models = new Dictionary<string, Model>();
        Dictionary<string, Texture2D> textures = new Dictionary<string, Texture2D>();

        static Random rand;
        public static Random Rand
        {
            get
            {
                if (rand == null) rand = new Random();
                return rand;
            }
        }

        SpriteFont font;
        public SpriteFont Font { get { return font; } }

        Song backgroundSong;
        bool playingSong = false;
        bool playSong = false;

        int shipPushing = 0;

        Vector3 mousePosition = Vector3.Zero;
        
        public Stage ProgramStage { get; set; }

        PostProcessor postProcessor;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        #region Init
        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            ProgramStage = Stage.Menu;

            // TODO: Add your initialization logic here
            //CreateGame(numSolar: 2, planetPerSolar: 10, playerPerSolar:4);

            Services.AddService(typeof(IGameActions), this);
            Services.AddService(typeof(IGraphics), this);

            Components.Add(new InputComponent(this));
            
            Components.Add(new DataComponent(this));

            Components.Add(new StateComponent(this));
            Components.Add(new CameraComponent(this));

            Components.Add(new MenuComponent(this));

            Components.Add(new MainMenuComponent(this));

            base.Initialize();
        }

        #endregion

        #region Content
        protected void LoadModel(string s, Effect effect = null)
        {
            Model m = Content.Load<Model>("models/"+s);
            models.Add(s, m);
            if (effect != null)
            {
                foreach (ModelMesh mesh in m.Meshes)
                    foreach (ModelMeshPart part in mesh.MeshParts)
                        part.Effect = effect;
            }
        }
        protected void LoadTexture(string s)
        {
            textures.Add(s, Content.Load<Texture2D>("textures/"+s));
        }
        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            backgroundSong = Content.Load<Song>("noise");
            MediaPlayer.IsRepeating = true;

            LoadModel("ball");
            LoadModel("redball");
            LoadModel("greenball");
            LoadModel("ship");
            LoadModel("missile");

            LoadTexture("cursor");
            LoadTexture("flag1");
            LoadTexture("flaggreen1");
            LoadTexture("flagblue1");

            LoadTexture("stars");
            LoadTexture("star2");

            LoadTexture("earth");
            LoadTexture("sun");

            LoadTexture("lava");
            LoadTexture("dirt");
            LoadTexture("desert");
            LoadTexture("snow");

            LoadModel("flagship");
            LoadTexture("targeted");

            
            font = Content.Load<SpriteFont>("font");
            Effect effect = Content.Load<Effect>("effects/Effect");
            Effect texEffect = Content.Load<Effect>("effects/TextureEffect");
            foreach (ModelMesh mesh in models["ball"].Meshes)
                foreach (ModelMeshPart part in mesh.MeshParts)
                    part.Effect = texEffect;

            LoadModel("flag");

            postProcessor = new PostProcessor(GraphicsDevice, Content);

            MakeRenderTarget();
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            Content.Unload();
        }
        #endregion

        public void SetOption(string s, object o)
        {
            switch (s)
            {
                case "playSong":
                    playSong = (bool)o;
                    break;
            }
        }

        public void ProcessMenuItem(MenuItem item)
        {
            switch (item.Command)
            {
                case "buy_ship":
                    {
                        IData data = Services.GetService<IData>();
                        data.MainPlayer.BuyShip(item.Argument as string);
                    }
                    break;
                case "colonize":
                    {
                        IData data = Services.GetService<IData>();
                        Planet p = null;
                        foreach (SolarSystem s in data.SolarSystems)
                        {
                            p = s.Planets.Find(planet => planet.Player.Neutral && (planet.Position - data.MainPlayer.FlagShip.Position).Length() <= C.ColonizeDistance);
                            if (p != null) break;
                        }

                        data.MainPlayer.Colonize(p);
                    }
                    break;
                case "build":
                    {
                        IData data = Services.GetService<IData>();
                        data.MainPlayer.BuyShip(item.Argument as string);
                    }
                    break;
                case "buy_pirate":
                    {
                        IData data = Services.GetService<IData>();
                        data.MainPlayer.BuyPirate(item.Argument as string);
                    }
                    break;
            }
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            float time = (float)gameTime.ElapsedGameTime.TotalSeconds;

            IDataInput input = (IDataInput)Services.GetService(typeof(IDataInput));

            if(ProgramStage == Stage.Game)
            {
                // Allows the game to exit
                if (input.IsKeyClicked(Keys.Escape))
                {
                    ProgramStage = Stage.Menu;
                    MediaPlayer.Stop();
                    playingSong = false;
                }
                else
                {
                    if (playSong != playingSong)
                    {
                        if (playSong)
                        {
                            MediaPlayer.Play(backgroundSong);
                        }
                        else
                        {
                            MediaPlayer.Stop();
                        }
                        playingSong = playSong;
                    }
                    ICamera camera = Services.GetService<ICamera>();

                    Vector2 mousePoint = input.MousePosition;
                    if (input.ScrollWheelDelta != 0)
                        camera.Zoom(ref mousePoint, (input.ScrollWheelDelta) / 10.0f, GraphicsDevice.Viewport);

                    if (input.IsButtonPressed(Buttons.RightStick))
                    {
                        Vector2 z = new Vector2(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
                        float height = 500;
                        camera.Zoom(ref z, camera.Position.Y > height ? height : -height, GraphicsDevice.Viewport);
                    }

                    Vector3 near = GraphicsDevice.Viewport.Unproject(new Vector3(mousePoint, 0), camera.Projection, camera.View, Matrix.Identity);
                    Vector3 far = GraphicsDevice.Viewport.Unproject(new Vector3(mousePoint, 1), camera.Projection, camera.View, Matrix.Identity);
                    Vector3 dir = far - near;
                    dir.Normalize();
                    Ray ray = new Ray(near, dir);

                    mousePosition = ray.Position - (ray.Position.Y / ray.Direction.Y) * ray.Direction;

                    IData data = Services.GetService<IData>();

                    //int t = shipPushing;
                    //for (int i = 0; i < 10; i++)
                    //{
                    //    if (shipPushing >= ships.Count) shipPushing = 0;
                    //    else
                    //    {
                    //        //ships[shipPushing].Push(ships);
                    //        //ships[shipPushing].PickTarget(ships);
                    //        ships[shipPushing].ConquerPlanet(planets);
                    //        shipPushing++;
                    //    }
                    //    if (shipPushing == t) break;
                    //}
                }
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {

            //GraphicsDevice.SetRenderTarget(renderTarget);
            //GraphicsDevice.Clear(Color.Black);

            if(ProgramStage == Stage.Game)
            {
                spriteBatch.Begin();
                Texture2D tex = textures["star2"];
                Rectangle dest = new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
                Rectangle source = new Rectangle(0, 0, tex.Width, tex.Height);
                spriteBatch.Draw(tex, dest, source, Color.White);
                spriteBatch.End();

                GraphicsDevice.BlendState = BlendState.Opaque;
                GraphicsDevice.DepthStencilState = DepthStencilState.Default;

                IData data = (IData)Services.GetService(typeof(IData));

                ICamera camera = Services.GetService<ICamera>();
                Matrix projection = camera.Projection;
                Matrix view = camera.View;

                foreach (SolarSystem ss in data.SolarSystems)
                {
                    ss.Draw(ref projection, ref view);
                    foreach (Planet p in ss.Planets)
                        p.Draw();

                    foreach (Building b in ss.Buildings)
                        b.Draw();
                }

                Vector3 lightDir;
                foreach (Ship ship in data.Ships)
                {
                    
                    lightDir = Vector3.Normalize(data.SolarSystems.First().Position - ship.Position);
                    ship.Draw(ref projection, ref view, ref lightDir);
                    //models["ship"].Draw(Matrix.CreateRotationY(ship.Angle) * Matrix.CreateTranslation(ship.Position.X, ship.Position.Y, ship.Position.Z), view, projection);
                }

                foreach (Player p in data.Players)
                {
                    lightDir = Vector3.Normalize(data.SolarSystems.First().Position - p.FlagShip.Position);
                    p.FlagShip.Draw(ref projection, ref view, ref lightDir);

                    foreach (CapitalShip ship in p.MainFleet)
                    {
                        if (ship.Alive && ship.Built)
                        {
                            lightDir = Vector3.Normalize(data.SolarSystems.First().Position - ship.Position);
                            ship.Draw(ref projection, ref view, ref lightDir);
                        }
                    }
                }

                foreach (Weapon m in data.Missiles)
                {
                    m.Draw();
                }

                spriteBatch.Begin();

                spriteBatch.DrawString(font, "Credit: " + data.MainPlayer.Credit, new Vector2(620, 30), Color.White);

                foreach (SolarSystem s in data.SolarSystems)
                {
                    Planet p = s.Planets.Find(planet => planet.Player.Neutral && (planet.Position - data.MainPlayer.FlagShip.Position).Length() <= C.ColonizeDistance);
                    if (p!=null)
                    {
                        spriteBatch.DrawString(font, "Press X to colonize", camera.Project(p.Position), data.MainPlayer.MainFleet.Exists(ship => ship.Built && ship.Alive && ship.Colony) ? Color.White : Color.Gray);
                        break;
                    }
                }

                foreach (Positioned p in data.MainPlayer.Targets)
                {
                    Vector3 pos = GraphicsDevice.Viewport.Project(p.Position, projection, view, Matrix.Identity);
                    spriteBatch.Draw(textures["targeted"], new Rectangle((int)pos.X - 10, (int)pos.Y - 10, 20, 20), Color.White);
                }

                foreach(SolarSystem s in data.SolarSystems)
                    foreach (Planet p in s.Planets)
                    {
                        Vector3 pos = GraphicsDevice.Viewport.Project(p.Position, projection, view, Matrix.Identity);
                        //spriteBatch.DrawString(font, p.Name, new Vector2(pos.X - font.MeasureString(p.Name).X / 2, pos.Y), p.Player.Color);
                        Color border = p.Player.Color;
                        float factor = 1.2f;
                        byte shift = 50;
                        border.R = (byte)((border.R + shift) * factor);
                        border.G = (byte)((border.G + shift) * factor);
                        border.B = (byte)((border.B + shift) * factor);
                        DrawBorderText(p.Name, new Vector2(pos.X - font.MeasureString(p.Name).X / 2, pos.Y), p.Player.Color, border, 1);

                        spriteBatch.DrawString(font, ((int)p.Population).ToString(), new Vector2(pos.X, pos.Y - 10), Color.White);
                    }
                IDataInput input = (IDataInput)Services.GetService(typeof(IDataInput));
                spriteBatch.Draw(textures["cursor"], input.MousePosition, Color.White);

                spriteBatch.DrawString(font, string.Format
                ("Flagship Level: {0}\nExp: {1}/{2}", data.MainPlayer.FlagShip.Level, data.MainPlayer.FlagShip.Experience, data.MainPlayer.FlagShip.ExperienceToLevel(data.MainPlayer.FlagShip.Level)), new Vector2(300, 400), Color.WhiteSmoke);

                switch (Services.GetService<IState>().State)
                {
                    case GameState.Win:
                        DrawOnCenterBordered("Congratulations!", 3, 100, Color.Yellow, Color.Black, 2);
                        DrawOnCenterBordered("You have conquered all!", 2.5f, 200, Color.White, Color.Black, 2);
                        DrawOnCenterBordered("Press Escape to continue", 1, 300, Color.White, Color.Black, 1);
                        break;
                    case GameState.Lost:
                        DrawOnCenterBordered("Press Escape to continue", 1, 300, Color.White, Color.Black, 1);
                        DrawOnCenterBordered("Another has conquered all!", 2.5f, 200, Color.White, Color.Black, 2);
                        break;
                    default:
                        break;
                }
                spriteBatch.End();
            }

            base.Draw(gameTime);

            //postProcessor.ToneMap(renderTarget, null, (float)gameTime.ElapsedGameTime.TotalSeconds, true, true);
        }

        void DrawOnCenter(string text, float scale, float y, Color color)
        {
            spriteBatch.DrawString(font, text, new Vector2((GraphicsDevice.Viewport.Width - font.MeasureString(text).X * scale) / 2, y), color, 0, new Vector2(0), scale, SpriteEffects.None, 0);
        }
        void DrawOnCenterBordered(string text, float scale, float y, Color color, Color borderColor, float borderThickness)
        {
            Vector2 pos = new Vector2((GraphicsDevice.Viewport.Width - font.MeasureString(text).X * scale) / 2, y);
            spriteBatch.DrawString(font, text, pos - new Vector2(-borderThickness, 0), borderColor, 0, new Vector2(0), scale, SpriteEffects.None, 0);
            spriteBatch.DrawString(font, text, pos - new Vector2(borderThickness, 0), borderColor, 0, new Vector2(0), scale, SpriteEffects.None, 0);
            spriteBatch.DrawString(font, text, pos - new Vector2(0, -borderThickness), borderColor, 0, new Vector2(0), scale, SpriteEffects.None, 0);
            spriteBatch.DrawString(font, text, pos - new Vector2(0, borderThickness), borderColor, 0, new Vector2(0), scale, SpriteEffects.None, 0);
            spriteBatch.DrawString(font, text, pos, color, 0, new Vector2(0), scale, SpriteEffects.None, 0);
        }
        void DrawBorderText(string text, Vector2 position, Color color, Color borderColor, float borderThickness)
        {
            spriteBatch.DrawString(font, text, position - new Vector2(-borderThickness, 0), borderColor);
            spriteBatch.DrawString(font, text, position - new Vector2(borderThickness, 0), borderColor);
            spriteBatch.DrawString(font, text, position - new Vector2(0, -borderThickness), borderColor);
            spriteBatch.DrawString(font, text, position - new Vector2(0, borderThickness), borderColor);
            spriteBatch.DrawString(font, text, position, color);
        }

        RenderTarget2D renderTarget;
        private void MakeRenderTarget()
        {
            if (renderTarget != null)
                renderTarget.Dispose();
            postProcessor.FlushCache();

            // LogLuv is encoded in a standard R8G8B8A8 surface                 
            renderTarget = new RenderTarget2D(GraphicsDevice,
            GraphicsDevice.PresentationParameters.BackBufferWidth,
            GraphicsDevice.PresentationParameters.BackBufferHeight,
            false,
            SurfaceFormat.Color,
            DepthFormat.Depth24Stencil8,
            1,
            RenderTargetUsage.DiscardContents);
        }
    }
}
